package controllers;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import common._HashMap;
import common.annotation.UI_Field;
import common.annotation.UI_Model;
import models._Model;
import play.Logger;
import play.Play;
import play.data.binding.Binder;
import play.data.validation.MaxSize;
import play.data.validation.Password;
import play.data.validation.Required;
import play.db.Model;
import play.db.Model.Factory;
import play.exceptions.TemplateNotFoundException;
import play.mvc.Before;
import play.mvc.Controller;
import play.mvc.Router;
import play.utils.Java;

public abstract class PTcrud extends Controller {
	
	/**
	 * 获取模块列表
	 * 
	 */
	public static void modelListJSON()
	{
		List<String> objs = new ArrayList<>() ;
		try {
			List<Class> list = Play.classloader.getAssignableClasses( Play.classloader.loadClass("controllers.PTcrud") ) ;
			for( Class clz: list ) {
				if ( clz.getSimpleName().contains("Trip"))
					objs.add( clz.getSimpleName() ) ;
			}
			renderJSON( objs );
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 显示列表 (getPageSize 默认30)
	 * 
	 * @param page
	 * @param search
	 * @param searchFields
	 * @param orderBy
	 * @param order
	 */
    public static void listJSON(int page, String search, String searchFields, String orderBy, String order) {
    	
        ObjectType type = ObjectType.get(getControllerClass());
        
        notFoundIfNull(type);
        if (page < 1) {
            page = 1;
        }
        
        List<_Model> objects = type.findPage(page, search, searchFields, orderBy, order, (String) request.args.get("where"));
        Long count = type.count(search, searchFields, (String) request.args.get("where"));
        Long totalCount = type.count(null, null, (String) request.args.get("where"));
        
        renderJSON( new _HashMap<>()
        		.add( "objects", objects)
        		.add( "count", count)
        		.add("totalCount", totalCount)
        		.add("page", page)
        		.add("orderBy", orderBy)
        		.add("order", order) 
        		);
    }
    
	/**
	 * 所有数据列表
	 * 
	 */
    public static void allJSON() throws Exception {
    	
        ObjectType type = ObjectType.get(getControllerClass());
        
        notFoundIfNull(type);
        
        List<_Model> _models = (List<_Model>) type.entityClass.getMethod( "findAll").invoke(null) ;
        _models.stream().filter(_model-> _model._flag == 1).collect(Collectors.toList());
 
        renderJSON( new _HashMap<>()
        		.add( "objects", _models )
        		);
    }
    
    /**
     * 保存
     * @throws Exception
     */
    public static void createJSON() throws Exception {
        ObjectType type = ObjectType.get(getControllerClass());
        notFoundIfNull(type);
        Constructor<?> constructor = type.entityClass.getDeclaredConstructor();
        constructor.setAccessible(true);
        _Model object = (_Model) constructor.newInstance();
        Binder.bindBean(params.getRootParamNode(), "object", object);
        object.__save();
        renderJSON( new _HashMap<>()
        					.add( "created", true )
        					.add( "id",  object._key() )
        		);
    }
	
    /**
     * 显示数据
     * @param id
     * @throws Exception
     */
    public static void showJSON(String id) throws Exception {
    	
        ObjectType type = ObjectType.get(getControllerClass());
        notFoundIfNull(type);
        _Model object = type.findById(id);
        notFoundIfNull( object._flag == 0?null:object );
        
    	renderJSON( new _HashMap<>()
    					.add( "objects", object)
            		);
    }
    
    protected static void renderJSON(Object o) {
        throw new  _RenderJson(o);
    }
    
    
    /**
     * 获取type信息
     * @throws Exception
     */
    public static void typeInfoJSON() throws Exception {
        ObjectType type = ObjectType.get(getControllerClass());
        notFoundIfNull(type);
        
        renderJSON( new _HashMap<>()
        					.add( "type", type.toMap() )
        		);
    }
    
    /**
     * 更新
     * @param id
     * @throws Exception
     */
    public static void updateJSON(String id) throws Exception {
    	
        ObjectType type = ObjectType.get(getControllerClass());
        notFoundIfNull(type);
        _Model object = type.findById(id);
        notFoundIfNull(object);
        Binder.bindBean(params.getRootParamNode(), "object", object);
        object.__save();
        renderJSON( new _HashMap<>()
        					.add( "updated", true )
        		);
    }
    
	/**
	 *    
	 * @param id
	 * @throws Exception
	 */
    public static void deleteJSON(String id) throws Exception {
        ObjectType type = ObjectType.get(getControllerClass());
        notFoundIfNull(type);
        _Model object = type.findById(id);
        notFoundIfNull( object._flag == 0?null:object );
        try {
            object.__delete();
        } catch (Exception e) {
        	renderJSON( new _HashMap<>()
						.add( "deleted", false )
						.add( "reson", e.getMessage() )
        			);
        }
        renderJSON( new _HashMap<>()
							.add( "deleted", true )
        		);
    }
    
	/**
	 * 批量删除
	 * 
	 * @param ids
	 * @throws Exception
	 */
    public static void batchDeleteJSON(String ids) throws Exception {
        ObjectType type = ObjectType.get(getControllerClass());
        notFoundIfNull(type);
        
        String[] idArr = ids.split(",") ;
        
        List<String> failIds = new ArrayList<>() ;
        for (String id : idArr) {
        	_Model object = type.findById(id);
        	try {
        		object.__delete();
        	} catch (Exception e) {
        		failIds.add(id) ;
        	}
		}
        
        if ( failIds.size()>0  )
        {
        	 renderJSON( new _HashMap<>()
						.add( "failIds", failIds )
						.add( "deleted", false )
        			 );
        }
        
        renderJSON( new _HashMap<>()
							.add( "deleted", true )
        		);
    }
    
    
    @Before
    public static void addType() throws Exception {
        ObjectType type = ObjectType.get(getControllerClass());
        renderArgs.put("type", type);
    }
    
    public static void index() {
        if (getControllerClass() == PTcrud.class) {
            forbidden();
        }
        render("PTcrud/member-list.html");
    }
    
    public static void list(int page, String search, String searchFields, String orderBy, String order) {
        ObjectType type = ObjectType.get(getControllerClass());
        notFoundIfNull(type);
        if (page < 1) {
            page = 1;
        }
        List<_Model> objects = type.findPage(page, search, searchFields, orderBy, order, (String) request.args.get("where"));
        Long count = type.count(search, searchFields, (String) request.args.get("where"));
        Long totalCount = type.count(null, null, (String) request.args.get("where"));
        try {
//            render(type, objects, count, totalCount, page, orderBy, order);
            render("PTcrud/PTlist.html", type, objects, count, totalCount, page, orderBy, order);
        } catch (TemplateNotFoundException e) {
            render("PTcrud/PTlist.html", type, objects, count, totalCount, page, orderBy, order);
        }
    }

    public static void show(String id) throws Exception {
        ObjectType type = ObjectType.get(getControllerClass());
        notFoundIfNull(type);
        _Model object = type.findById(id);
        notFoundIfNull(object);
        try {
            render("PTcrud/PTshow.html", type, object);
        } catch (TemplateNotFoundException e) {
        	render("PTcrud/PTshow.html", type, object);
        }
    }

    @SuppressWarnings("deprecation")
    public static void attachment(String id, String field) throws Exception {
        ObjectType type = ObjectType.get(getControllerClass());
        notFoundIfNull(type);
        _Model object = type.findById(id);
        notFoundIfNull(object);
        Object att = object.getClass().getField(field).get(object);
        if(att instanceof _Model.BinaryField) {
            _Model.BinaryField attachment = (_Model.BinaryField)att;
            if (attachment == null || !attachment.exists()) {
                notFound();
            }
            response.contentType = attachment.type();
            renderBinary(attachment.get(), attachment.length());
        }
        // DEPRECATED
        if(att instanceof play.db.jpa.FileAttachment) {
            play.db.jpa.FileAttachment attachment = (play.db.jpa.FileAttachment)att;
            if (attachment == null || !attachment.exists()) {
                notFound();
            }
            renderBinary(attachment.get(), attachment.filename);
        }
        notFound();
    }

    public static void save(String id) throws Exception {
        ObjectType type = ObjectType.get(getControllerClass());
        notFoundIfNull(type);
        _Model object = type.findById(id);
        notFoundIfNull(object);
        Binder.bindBean(params.getRootParamNode(), "object", object);
        validation.valid(object);
        if (validation.hasErrors()) {
            renderArgs.put("error", play.i18n.Messages.get("crud.hasErrors"));
            try {
                render(request.controller.replace(".", "/") + "/show.html", type, object);
            } catch (TemplateNotFoundException e) {
                render("PTcrud/show.html", type, object);
            }
        }
        object._save();
        flash.success(play.i18n.Messages.get("crud.saved", type.modelName));
        if (params.get("_save") != null) {
            redirect(request.controller + ".list");
        }
        redirect(request.controller + ".show", object._key());
    }

    public static void blank() throws Exception {
        ObjectType type = ObjectType.get(getControllerClass());
        notFoundIfNull(type);
        Constructor<?> constructor = type.entityClass.getDeclaredConstructor();
        constructor.setAccessible(true);
        _Model object = (_Model) constructor.newInstance();
        try {
            render(type, object);
        } catch (TemplateNotFoundException e) {
            render("PTcrud/blank.html", type, object);
        }
    }

    public static void create() throws Exception {
        ObjectType type = ObjectType.get(getControllerClass());
        notFoundIfNull(type);
        Constructor<?> constructor = type.entityClass.getDeclaredConstructor();
        constructor.setAccessible(true);
        _Model object = (_Model) constructor.newInstance();
        Binder.bindBean(params.getRootParamNode(), "object", object);
        validation.valid(object);
        if (validation.hasErrors()) {
            renderArgs.put("error", play.i18n.Messages.get("crud.hasErrors"));
            try {
                render(request.controller.replace(".", "/") + "/blank.html", type, object);
            } catch (TemplateNotFoundException e) {
                render("PTcrud/blank.html", type, object);
            }
        }
        object._save();
        flash.success(play.i18n.Messages.get("crud.created", type.modelName));
        if (params.get("_save") != null) {
            redirect(request.controller + ".list");
        }
        if (params.get("_saveAndAddAnother") != null) {
            redirect(request.controller + ".blank");
        }
        redirect(request.controller + ".show", object._key());
    }

    public static void delete(String id) throws Exception {
        ObjectType type = ObjectType.get(getControllerClass());
        notFoundIfNull(type);
        _Model object = type.findById(id);
        notFoundIfNull(object);
        try {
            object._delete();
        } catch (Exception e) {
            flash.error(play.i18n.Messages.get("crud.delete.error", type.modelName));
            redirect(request.controller + ".show", object._key());
        }
        flash.success(play.i18n.Messages.get("crud.deleted", type.modelName));
        redirect(request.controller + ".list");
    }

    protected static ObjectType createObjectType(Class<? extends _Model> entityClass) {
        return new ObjectType(entityClass);
    }

    // ~~~~~~~~~~~~~
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface For {
        Class<? extends _Model> value();
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface Exclude {}

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface Hidden {}

    // ~~~~~~~~~~~~~
    static int getPageSize() {
        return Integer.parseInt(Play.configuration.getProperty("crud.pageSize", "30"));
    }
    

    public static class ObjectType implements Comparable<ObjectType> {

        public Class<? extends Controller> controllerClass;
        public Class<? extends _Model> entityClass;
        public String name;
        public String modelName;
        public String controllerName;
        public String keyName;
		public Factory factory;
        public UI_Model uiModel ;

        public ObjectType(Class<? extends _Model> modelClass) {
            this.modelName = modelClass.getSimpleName();
            this.entityClass = modelClass;
            this.factory = _Model.Manager.factoryFor(entityClass);
            this.keyName = factory.keyName();
        }

        @SuppressWarnings("unchecked")
        public ObjectType(String modelClass) throws ClassNotFoundException {
            this((Class<? extends _Model>) Play.classloader.loadClass(modelClass));
        }

        public static ObjectType forClass(String modelClass) throws ClassNotFoundException {
            return new ObjectType(modelClass);
        }

        public static ObjectType get(Class<? extends Controller> controllerClass) {
            Class<? extends _Model> entityClass = getEntityClassForController(controllerClass);
            if (entityClass == null || !_Model.class.isAssignableFrom(entityClass)) {
                return null;
            }
            ObjectType type;
            try {
                type = (ObjectType) Java.invokeStaticOrParent(controllerClass, "createObjectType", entityClass);
            } catch (Exception e) {
                Logger.error(e, "Couldn't create an ObjectType. Use default one.");
                type = new ObjectType(entityClass);
            }
            type.name = controllerClass.getSimpleName().replace("$", "");
            type.controllerName = controllerClass.getSimpleName().toLowerCase().replace("$", "");
            type.controllerClass = controllerClass;
            
            type.uiModel = type.entityClass.getDeclaredAnnotation(UI_Model.class) ;
            return type;
        }

        @SuppressWarnings("unchecked")
        public static Class<? extends _Model> getEntityClassForController(Class<? extends Controller> controllerClass) {
            if (controllerClass.isAnnotationPresent(For.class)) {
                return controllerClass.getAnnotation(For.class).value();
            }
            for(Type it : controllerClass.getGenericInterfaces()) {
                if(it instanceof ParameterizedType) {
                    ParameterizedType type = (ParameterizedType)it;
                    if (((Class<?>)type.getRawType()).getSimpleName().equals("CRUDWrapper")) {
                        return (Class<? extends _Model>)type.getActualTypeArguments()[0];
                    }
                }
            }
            String name = controllerClass.getSimpleName().replace("$", "");
            name = "models." + name.substring(0, name.length() - 1);
            try {
                return (Class<? extends _Model>) Play.classloader.loadClass(name);
            } catch (ClassNotFoundException e) {
                return null;
            }
        }

        public Object getListAction() {
            return Router.reverse(controllerClass.getName().replace("$", "") + ".list");
        }

        public Object getBlankAction() {
            return Router.reverse(controllerClass.getName().replace("$", "") + ".blank");
        }

        public Long count(String search, String searchFields, String where) {
        	if ( where == null) where = "" ;
        	where +=" _flag =1 " ;
            return factory.count(searchFields == null ? new ArrayList<String>() : Arrays.asList(searchFields.split("[ ]")), search, where);
        }

        @SuppressWarnings("unchecked")
        public List<_Model> findPage(int page, String search, String searchFields, String orderBy, String order, String where) {
            int offset = (page - 1) * getPageSize();
            List<String> properties = searchFields == null ? new ArrayList<String>(0) : Arrays.asList(searchFields.split("[ ]"));
            if ( where == null) where = "" ;
        	where +=" _flag =1 " ;
        	if ( orderBy==null ) orderBy = "" ;
        	orderBy +=" _updateTime " ;
        	order = "DESC" ;
        	
            List<Model> models = Model.Manager.factoryFor(entityClass).fetch(offset, getPageSize(), orderBy, order, properties, search, where) ;
            
            List<_Model> _models = new ArrayList<>() ;
            models.forEach( model ->{
            	_models.add( (_Model)model ) ;
            });
            return _models;
        }

        public _Model findById(String id) throws Exception {
            if (id == null) {
                return null;
            }

            Factory factory =  _Model.Manager.factoryFor(entityClass);
            Object boundId = Binder.directBind(id, factory.keyType());
            
            return (_Model)factory.findById(boundId);
        }


        public List<ObjectField> getFields() {
        	
            List<ObjectField> fields = new ArrayList<ObjectField>();
            for (_Model.Property f : factory.listProperties()) {
                ObjectField of = new ObjectField(f);
                fields.add(of);
            }

            return fields;
        }

        public ObjectField getField(String name) {
            for (ObjectField field : getFields()) {
                if (field.name.equals(name)) {
                    return field;
                }
            }
            return null;
        }

        @Override
        public int compareTo(ObjectType other) {
            return modelName.compareTo(other.modelName);
        }

        @Override
        public String toString() {
            return modelName;
        }

        public static class ObjectField {

            private _Model.Property property;
            public String type = "unknown";
            public String name;
            public boolean multiple;
            public boolean required;
            public UI_Field uiFiled ;

            @SuppressWarnings("deprecation")
            public ObjectField(_Model.Property property ) {
                Field field = property.field;
                this.property = property;
                if (CharSequence.class.isAssignableFrom(field.getType())) {
                    type = "text";
                    if (field.isAnnotationPresent(MaxSize.class)) {
                        int maxSize = field.getAnnotation(MaxSize.class).value();
                        if (maxSize > 100) {
                            type = "longtext";
                        }
                    }
                    if (field.isAnnotationPresent(Password.class)) {
                        type = "password";
                    }
                }
                if (Number.class.isAssignableFrom(field.getType()) || field.getType().equals(double.class) || field.getType().equals(int.class) || field.getType().equals(long.class)) {
                    type = "number";
                }
                if (Boolean.class.isAssignableFrom(field.getType()) || field.getType().equals(boolean.class)) {
                    type = "boolean";
                }
                if (Date.class.isAssignableFrom(field.getType())) {
                    type = "date";
                }
                if (property.isRelation) {
                    type = "relation";
                }
                if (property.isMultiple) {
                    multiple = true;
                }
                if(_Model.BinaryField.class.isAssignableFrom(field.getType()) || /** DEPRECATED **/ play.db.jpa.FileAttachment.class.isAssignableFrom(field.getType())) {
                    type = "binary";
                }
                if (field.getType().isEnum()) {
                    type = "enum";
                }
                if (property.isGenerated) {
                    type = null;
                }
                if (field.isAnnotationPresent(Required.class)) {
                    required = true;
                }
                if (field.isAnnotationPresent(Hidden.class)) {
                    type = "hidden";
                }
                if (field.isAnnotationPresent(Exclude.class)) {
                    type = null;
                }
                if (java.lang.reflect.Modifier.isFinal(field.getModifiers())) {
                    type = null;
                }
                name = field.getName();
                
                this.uiFiled = this.property.field.getDeclaredAnnotation(UI_Field.class) ;
            }

            public List<Object> getChoices() {
                return property.choices.list();
            }
        }
        
        public Map<String,Object> toMap() {
        	
        	Map<String,Object> result = new _HashMap<String,Object>()
															.add( "modelName", this.modelName)
															.add( "fields",  new ArrayList<>() ) ;
        	
        	for( Annotation ann : this.entityClass.getAnnotationsByType(UI_Model.class) )
        	{
        		if ( ann != null && ann.getClass()!=null
						&& ann.toString().length() > 0 )
				{
        			Method[] methods = UI_Model.class.getDeclaredMethods() ;
					for (Method m : methods) {
						try {
							ObjectType obj = this.get(this.controllerClass) ;
							result.put( m.getName(), m.invoke( obj.uiModel, null) ) ;
						} catch (IllegalAccessException | IllegalArgumentException
								| InvocationTargetException e) {
							e.printStackTrace();
						}
					}
				}
        	}
        	
        	
        	this.getFields().forEach( field->{
        		
        		if ( field.name.indexOf("_flag")!=0 && field.uiFiled != null)
        		{
        			
        			_HashMap res = new _HashMap<String, Object>()
															.add( "name", field.name)
															.add( "type", field.type)
															;
        			
        			for ( Annotation annotation : field.property.field.getAnnotationsByType(UI_Field.class)) {
        				if ( annotation != null && annotation.getClass()!=null
        						&& annotation.toString().length() > 0 )
        				{
        					
        					Method[] methods = UI_Field.class.getDeclaredMethods() ;
        					for (Method m : methods) {
        						try {
									res.put( m.getName(), m.invoke( field.uiFiled, null) ) ;
								} catch (IllegalAccessException | IllegalArgumentException
										| InvocationTargetException e) {
									e.printStackTrace();
								}
							}
        				}
					}
        			
        			((List)result.get("fields")).add( res ) ;
        		}
        	});
        	
        	
        	
        	return result;
        			
        }
    }
}

